package metadatafactroy

import (
	"container/list"
	"encoding/json"
	"fmt"
	"gitee.com/ichub/webcli/common/base/baseconfig"
	"gitee.com/ichub/webcli/common/base/baseconsts"
	"gitee.com/ichub/webcli/common/base/baseutils/stringutils"
	"gitee.com/ichub/webcli/common/base/metadata/ichubmetadata"
	"gitee.com/ichub/webcli/common/base/metadata/metadatacontext"
	"gitee.com/ichub/webcli/common/dbcontent/database"
	"gitee.com/ichub/webcli/common/ichublog"
	"github.com/jinzhu/gorm"
	"github.com/sirupsen/logrus"
	"log"
)

/*
@Title    文件名称: db_factroy.go
@Description  描述: 础数据库工厂

@Author  作者: leijianming@163.com  时间(2024-01-31 22:38:21)
@Update  作者: leijianming@163.com  时间(2024-01-31 22:38:21)
*/
type FactroyPostgres struct {
	DbClientDto *baseconfig.DbClientDto
	funcGetDb   database.FuncGetDb `json:"-"`

	Table        string
	TableComment *string

	Pkey            string
	PkeyType        string
	StringFieldName string `json:"-"`
	StringFieldLen  string `json:"-"`
}

func NewFactroyPostgres() *FactroyPostgres {
	return &FactroyPostgres{}
}

func (this *FactroyPostgres) IsMysql() bool {
	return this.DbClientDto.Dbtype == baseconsts.DB_TYPE_MYSQL
}

func (this *FactroyPostgres) IniDb(conn string) (dbinst *gorm.DB) {

	if this.IsMysql() {
		dbinst, _ = database.InitDbMysql(conn)
	} else {
		dbinst, _ = database.InitDbPostgres(conn)
	}
	return
}

func (this *FactroyPostgres) makeBase(structName string) string {
	s := `type %sBase struct{
}`
	return fmt.Sprintf(s, structName)
}

func (this *FactroyPostgres) printList(lst *list.List) {
	logrus.Println()
	for i := lst.Front(); i != nil; i = i.Next() {
		logrus.Println(i.Value)
	}
}

func (this *FactroyPostgres) findPGTableComment() *ichubmetadata.MetadataTable {
	sql := `SELECT 
		relname as table_name,
		obj_description(oid) as table_comment
		FROM pg_class
		WHERE relkind = 'r' and relname='%s'
	`
	sql = fmt.Sprintf(sql, this.Table)
	var tables ichubmetadata.MetadataTable
	e := this.funcGetDb().Model(&ichubmetadata.MetadataPkInfo{}).Raw(sql).Find(&tables).Error
	if e != nil {
		fmt.Println("***=" + e.Error())

	}
	return &tables
}

func (this *FactroyPostgres) findPGColumnComment() []ichubmetadata.MetadataColumn {
	s := `SELECT 
    c.relname as TableName,
    col_description ( a.attrelid, a.attnum ) AS ColumnComment,
    format_type ( a.atttypid, a.atttypmod ) AS ColumnType,
    a.attname AS ColumnName 
	FROM pg_class AS c,    pg_attribute AS a
	WHERE    a.attrelid = c.oid   AND a.attnum >0
 			and c.relname = '%s'  `
	s = fmt.Sprintf(s, this.Table)
	var cs []ichubmetadata.MetadataColumn
	e := this.funcGetDb().Model(&ichubmetadata.MetadataPkInfo{}).Raw(s).Find(&cs).Error
	if e != nil {
		fmt.Println("***=" + e.Error())
	}

	return cs
}

func (this *FactroyPostgres) String() string {
	s, _ := json.Marshal(this)
	return string(s)

}

func (this *FactroyPostgres) ToString() string {
	s, _ := json.MarshalIndent(this, "", "    ")
	return string(s)

}

/*double	double	double
float	float	float
int32	int	int32	使用可变长编码方式。编码负数时不够高效——如果你的字段可能含有负数，那么请使用sint32。
int64	long	int64	使用可变长编码方式。编码负数时不够高效——如果你的字段可能含有负数，那么请使用sint64。
unit32	int[1]	unit32	总是4个字节。如果数值总是比总是比228大的话，这个类型会比uint32高效。
unit64	long[1]	unit64	总是8个字节。如果数值总是比总是比256大的话，这个类型会比uint64高效。
sint32	int	int32	使用可变长编码方式。有符号的整型值。编码时比通常的int32高效。
sint64	long	int64	使用可变长编码方式。有符号的整型值。编码时比通常的int64高效。
fixed32	int[1]	unit32
fixed64	long[1]	unit64	总是8个字节。如果数值总是比总是比256大的话，这个类型会比uint64高效。
sfixed32	int	int32	总是4个字节。
sfixed64	long	int64	总是8个字节。
bool	boolean	bool
string	String	string	一个字符串必须是UTF-8编码或者7-bit ASCII编码的文本。
bytes
*/

//dbcontent type->go type

func (this *FactroyPostgres) makeModelColNewLst(columns *[]ichubmetadata.MetadataColumn, ifnil bool) *list.List {

	s := `
	entity.{{.colname}} = new({{.goType}})
	`
	lst := list.New()
	for _, v := range *columns {
		colname := stringutils.Case2Camel(v.ColumnName)

		goType := v.FindGoType(v.DataType)
		if v.ColumnName != this.Pkey {
			vars := make(map[string]interface{})
			vars["goType"] = goType
			vars["colname"] = colname
			lst.PushBack(stringutils.ParseTemplateString(s, vars))
		}
	}
	this.printList(lst)
	return lst
}

func (this *FactroyPostgres) makeModelGo(columns *[]ichubmetadata.MetadataColumn) *list.List {
	sn := fmt.Sprintf(stringutils.Case2Camel(this.Table))
	snbase := this.makeBase(sn)
	lst := list.New()
	lst.PushBack("/* 指定扩展结结构，单独存文件。生成时不会覆盖: */")

	lst.PushBack(fmt.Sprintf("//type %sBase struct {ModelBase}", sn))
	comment := fmt.Sprintf("/*  %s  */", *this.TableComment)
	lst.PushBack(comment)

	lst.PushBack(fmt.Sprintf("type %s%s struct {", "", stringutils.UcfirstCase2Camel(this.Table)))
	//lst.PushBack(fmt.Sprintf("\tmodel.ParamBase" ))
	lst.PushBack(fmt.Sprintf("//\t%sBase", sn))
	lst.PushBack(fmt.Sprintf("//\tModelBase"))

	for _, v := range *columns {
		col_comment := fmt.Sprintf("\t/*  %s  */", v.ColumnComment)
		lst.PushBack(col_comment)
		//timeStr :=v.Birthday.Format("2006-01-02 15:04:05")
		dt := v.FindGoType(v.DataType)

		col_def := v.ColumnName + ";type:" + v.ColumnType
		if v.ColumnName == this.Pkey {
			col_def = col_def + ";PRIMARY_KEY"
		}
		if this.IsMysql() {
			col_def = col_def + fmt.Sprintf(";comment:'%s'", v.ColumnComment)
		}
		//if true || this.Dbtype_mysql {
		if len(v.ColumnDefault) > 0 {
			if v.IfNumeric() {
				col_def = col_def + fmt.Sprintf(";default:%s", v.ColumnDefault)
			}
			if v.IfString() {
				col_def = col_def + fmt.Sprintf(";default:\\'%s\\'", v.ColumnDefault)
			}
		}
		//}

		json_def := v.ColumnName //utils.Lcfirst(utils.Case2Camel(v.ColumnName))
		if dt == "int64" {       //&& v.ColumnName == this.Pkey
			json_def = json_def + ",string"
		}
		lst.PushBack(fmt.Sprintf("\t%s *%s `gorm:\"column:%s\" json:\"%s\"`", stringutils.Case2Camel(v.ColumnName), dt, col_def, json_def))
	}
	lst.PushBack("}")

	fmt.Println(snbase)
	for i := lst.Front(); i != nil; i = i.Next() {
		fmt.Println(i.Value)
	}
	return lst
}

func (this *FactroyPostgres) makeModelBodyGo(columns *[]ichubmetadata.MetadataColumn) *list.List {

	lines := list.New()

	for _, c := range *columns {
		col_comment := fmt.Sprintf("\t/*  %s  */", c.ColumnComment)
		lines.PushBack(col_comment)
		dt := c.FindGoType(c.DataType)

		col_def := c.ColumnName + ";type:" + c.ColumnType
		if c.ColumnName == this.Pkey {
			col_def = col_def + ";PRIMARY_KEY"
		}
		if this.IsMysql() {
			col_def = col_def + fmt.Sprintf(";comment:'%s'", c.ColumnComment)
		}
		//if true || this.Dbtype_mysql {
		if len(c.ColumnDefault) > 0 {
			if c.IfNumeric() {
				col_def = col_def + fmt.Sprintf(";default:%s", c.ColumnDefault)
			}
			if c.IfString() {
				col_def = col_def + fmt.Sprintf(";default:\\'%s\\'", c.ColumnDefault)
			}
		}
		json_def := c.ColumnName
		if this.IsMysql() {
			//json_def = baseutils.Lcfirst(stringutils.Case2Camel(c.ColumnName))
		}
		if dt == "int64" { //&& c.ColumnName == this.Pkey
			json_def = json_def + ",string"
		}
		lines.PushBack(fmt.Sprintf("\t%s %s `gorm:\"column:%s\" json:\"%s\"`", stringutils.Case2Camel(c.ColumnName), dt, col_def, json_def))
	}

	for i := lines.Front(); i != nil; i = i.Next() {
		log.Println(i.Value)
	}
	return lines
}

func (this *FactroyPostgres) makeModelDtoGo(columns *[]ichubmetadata.MetadataColumn) *list.List {

	lines := list.New()

	for _, c := range *columns {
		col_comment := fmt.Sprintf("\t/*  %s  */", c.ColumnComment)
		lines.PushBack(col_comment)
		dt := c.FindGoType(c.DataType)

		col_def := c.ColumnName + ";type:" + c.ColumnType
		if c.ColumnName == this.Pkey {
			col_def = col_def + ";PRIMARY_KEY"
		}
		if this.IsMysql() {
			col_def = col_def + fmt.Sprintf(";comment:'%s'", c.ColumnComment)
		}
		if len(c.ColumnDefault) > 0 {
			if c.IfNumeric() {
				col_def = col_def + fmt.Sprintf(";default:%s", c.ColumnDefault)
			}
			if c.IfString() {
				col_def = col_def + fmt.Sprintf(";default:\\'%s\\'", c.ColumnDefault)
			}
		}
		json_def := c.ColumnName

		if dt == "int64" { //&& c.ColumnName == this.Pkey
			json_def = json_def + ",string"
		}
		if dt == "bool" {
			dt = "string"
			lines.PushBack(fmt.Sprintf("\t%s %s `gorm:\"column:%s\" json:\"%s\"`", stringutils.Case2Camel(c.ColumnName), dt, col_def, json_def))

		} else {
			lines.PushBack(fmt.Sprintf("\t%s %s `gorm:\"column:%s\" json:\"%s\"`", stringutils.Case2Camel(c.ColumnName), dt, col_def, json_def))
		}
	}

	for i := lines.Front(); i != nil; i = i.Next() {
		log.Println(i.Value)
	}
	return lines
}
func (this *FactroyPostgres) makeModelParamsGo(columns *[]ichubmetadata.MetadataColumn) *list.List {

	lines := list.New()

	for _, c := range *columns {
		col_comment := fmt.Sprintf("\t/*  %s  */", c.ColumnComment)
		lines.PushBack(col_comment)
		dt := c.FindGoType(c.DataType)

		col_def := c.ColumnName + ";type:" + c.ColumnType
		if c.ColumnName == this.Pkey {
			col_def = col_def + ";PRIMARY_KEY"
		}
		if this.IsMysql() {
			col_def = col_def + fmt.Sprintf(";comment:'%s'", c.ColumnComment)
		}
		if len(c.ColumnDefault) > 0 {
			if c.IfNumeric() {
				col_def = col_def + fmt.Sprintf(";default:%s", c.ColumnDefault)
			}
			if c.IfString() {
				col_def = col_def + fmt.Sprintf(";default:\\'%s\\'", c.ColumnDefault)
			}
		}
		json_def := c.ColumnName

		if dt == "int64" { //&& c.ColumnName == this.Pkey
			json_def = json_def + ",string"
		}
		if dt == "bool" {
			dt = "string"
			lines.PushBack(fmt.Sprintf("\t%s *%s `gorm:\"column:%s\" json:\"%s\"`", stringutils.Case2Camel(c.ColumnName), dt, col_def, json_def))

		} else {
			lines.PushBack(fmt.Sprintf("\t%s *%s `gorm:\"column:%s\" json:\"%s\"`", stringutils.Case2Camel(c.ColumnName), dt, col_def, json_def))
		}
	}

	for i := lines.Front(); i != nil; i = i.Next() {
		logrus.Info(i.Value)
	}
	return lines
}

func (this *FactroyPostgres) BuildModel() *DefineFactors {

	var columns = this.FindColumns()
	var models = this.makeModelBodyGo(columns) // ms = this.makeModelGo(cs)

	return &DefineFactors{
		Columns: columns,
		Models:  models,
	}

}

func (this *FactroyPostgres) MakeModelProto() *list.List {
	cs := this.FindColumns()
	lst := this.makeModelProto(cs)
	return lst

}
func (this *FactroyPostgres) MakeModelProtoBody(columns *[]ichubmetadata.MetadataColumn) *list.List {

	sn := fmt.Sprintf(stringutils.Case2Camel(this.Table))
	snbase := this.makeBase(sn)
	lst := list.New()
	i := 5
	for _, v := range *columns {
		log.Println(v.String())
		dt := this.FindProtoType(v.DataType)
		if dt == "" {
			dt = "string"
		}
		lst.PushBack(fmt.Sprintf("\t%s %s = %d;", dt, stringutils.Lcfirst(stringutils.Case2Camel(v.ColumnName)), i))
		i++
	}

	log.Println(snbase)
	for i := lst.Front(); i != nil; i = i.Next() {

		log.Println(i.Value)
	}
	return lst
}

func (this *FactroyPostgres) makeModelProto(columns *[]ichubmetadata.MetadataColumn) *list.List {
	sn := fmt.Sprintf(stringutils.Case2Camel(this.Table))
	snbase := this.makeBase(sn)
	lst := list.New()
	//lst.PushBack(fmt.Sprintf("type %s%s struct {", Capitalize(tmp.Dbname), Capitalize(tmp.Table)))
	lst.PushBack(fmt.Sprintf("message %sProto   {", stringutils.UcfirstCase2Camel(stringutils.Capitalize(this.Table))))

	i := 1
	for _, v := range *columns {
		//timeStr :=cd v.Birthday.Format("2006-01-02 15:04:05")
		fmt.Println(v.String())
		dt := this.FindProtoType(v.DataType)
		if dt == "" {
			log.Println("")
			dt = "string"
		}
		lst.PushBack(fmt.Sprintf("\t%s %s = %d;", dt, stringutils.Case2Camel(v.ColumnName), i))
		i = i + 1
	}
	lst.PushBack("}")
	fmt.Println(snbase)
	fmt.Println()
	for i := lst.Front(); i != nil; i = i.Next() {

		fmt.Println(i.Value)
	}
	return lst
}

func (this *FactroyPostgres) FindPgPkey(table string) []ichubmetadata.MetadataPkInfo {
	s := `SELECT
	pg_constraint.conname AS pkname,
	pg_attribute.attname AS colname,
	pg_type.typname AS typename
		FROM pg_constraint
		INNER JOIN pg_class ON pg_constraint.conrelid = pg_class.oid
		INNER JOIN pg_attribute ON pg_attribute.attrelid = pg_class.oid
		AND pg_attribute.attnum = pg_constraint.conkey [ 1 ]
		INNER JOIN pg_type ON pg_type.oid = pg_attribute.atttypid
	WHERE	pg_class.relname = '%s'
		AND pg_constraint.contype = 'p' `

	var pkInfos []ichubmetadata.MetadataPkInfo
	s = fmt.Sprintf(s, table)
	log.Println("Find PKey: " + s)
	e := this.funcGetDb().Model(&ichubmetadata.MetadataPkInfo{}).Raw(s).Find(&pkInfos).Error
	if e != nil {
		fmt.Println(e.Error())
	}

	log.Println("FindPgPkey: \n" + s)
	if len(pkInfos) == 0 {
		fmt.Println(fmt.Sprintf("pkInfos len=0 无主键（可能表%s不存在！） ", table))
	}
	log.Println(fmt.Sprintf("pkInfos: %d", len(pkInfos)))
	return pkInfos
}

func (this *FactroyPostgres) buildIf(columns *[]ichubmetadata.MetadataColumn) *list.List {
	lst := list.New()
	s := `
		if param.Param.%s != nil {
			dbc = dbc.Where("%s=?", *param.Param.%s)
		}`
	for _, v := range *columns {
		cc := stringutils.Case2Camel(v.ColumnName)
		//timeStr := v.Birthday.Format("2006-01-02 15:04:05")
		if v.IfString() {
			s = `
			if param.Param.%s != nil ||  *param.Param.%s != "" {
				dbc = dbc.Where("%s = ?", *param.Param.%s)
			}`
			s = fmt.Sprintf(s, cc, cc, v.ColumnName, cc)
		} else if v.IfNumeric() {
			s = `
			if param.Param.%s != nil  ||  *param.Param.%s != 0 {
				dbc = dbc.Where("%s=?", *param.Param.%s)
			}`
			s = fmt.Sprintf(s, cc, cc, v.ColumnName, cc)
		} else {
			s = `
			if param.Param.%s != nil {
				dbc = dbc.Where("%s=?", *param.Param.%s)
			}`
			s = fmt.Sprintf(s, cc, v.ColumnName, cc)
		}

		lst.PushBack(s)
	}
	return lst
}

func (this *FactroyPostgres) FindFields(table string, fields string) string {

	var metatable = this.FindMetadata(table)
	ichublog.Log(metatable.ToPrettyString())

	if len(fields) != 0 && fields != "*" {
		return fields
	}
	return metatable.ToFieldsString()
}
func (this *FactroyPostgres) iniDb() {
	if this.DbClientDto != nil {
		return
	}
	this.DbClientDto = Cfg.ReadIchubDb()
	metadatacontext.MetadataContextInst.DbType = this.DbClientDto.Dbtype

}
func (this *FactroyPostgres) FindMetadata(table string) *ichubmetadata.MetadataTable {
	var c, found = ichubmetadata.InstMetadataCache.CacheGet(table)
	if found {
		return c
	}
	var DbFindMeta = func() *ichubmetadata.MetadataTable {

		this.iniDb()

		this.Table = table
		var metadataTable = ichubmetadata.NewMetadataTable()
		metadataTable.Cols = *this.FindColumns()
		metadataTable.TableSchema = this.DbClientDto.Dbname
		metadataTable.TableName = table
		metadataTable.BuildGoFields()
		return metadataTable
	}
	var meta = DbFindMeta()
	//设置cache
	ichubmetadata.InstMetadataCache.CacheSet(table, meta)

	return meta

}

const sql_postgres = `select TABLE_NAME as table_name,
			 table_catalog as table_schema,
			 Column_Name  as column_name,
			 udt_name  as data_type,
			 crdb_sql_type  as column_type,  
			 character_maximum_length as char_max_len, 
			 ''  as column_key ,
			 column_default as column_default
			 from information_schema.Columns 
			 where table_catalog ='%s' and table_name='%s'`

func (this *FactroyPostgres) FindColumns() *[]ichubmetadata.MetadataColumn {

	var columns []ichubmetadata.MetadataColumn
	var sql string
	if this.IsMysql() {
		sql = fmt.Sprintf(sql_mysql, this.DbClientDto.Dbname, this.Table)
	} else {
		sql = fmt.Sprintf(sql_postgres, this.DbClientDto.Dbname, this.Table)

	}
	e := this.funcGetDb().Raw(sql).Find(&columns).Error
	if e != nil {
		logrus.Error(e.Error())
		return &columns
	}
	if len(columns) == 0 {
		logrus.Error(this.Table + " MetadataColumn len =0 (表不存在！)")
		return &columns
	}

	if this.IsMysql() {
		for _, v := range columns { //timeStr := v.Birthday.Format("2006-01-02 15:04:05")

			if v.ColumnKey == "PRI" || v.ColumnName == "id" {
				this.Pkey = v.ColumnName
				this.PkeyType = v.DataType
			}
			if v.IfString() {
				this.StringFieldName = v.ColumnName
				this.StringFieldLen = v.CharMaxLen
			}
		}
	} else { // pgsql
		pks := this.FindPgPkey(this.Table)
		log.Println(fmt.Sprintf("pkInfos: %d", len(pks)))
		this.Pkey = "id"
		this.PkeyType = "int64"
		if len(pks) > 0 {
			this.Pkey = pks[0].ColName
			this.PkeyType = pks[0].TypeName
		}

		cscomment := this.findPGColumnComment()
		for index := range columns { //timeStr := v.Birthday.Format("2006-01-02 15:04:05")
			for _, c := range cscomment {
				if columns[index].ColumnName == c.ColumnName {
					columns[index].ColumnComment = c.ColumnComment
				}
			}
			if columns[index].IfString() {
				this.StringFieldName = columns[index].ColumnName
				this.StringFieldLen = columns[index].CharMaxLen
			}
		}
	}

	return &columns
}

func (this *FactroyPostgres) FindTableComment() {
	if this.IsMysql() { //this.TableComment == nil &&
		tbls := this.FindTables()
		for _, v := range tbls {
			if this.Table == v.TableName {
				this.TableComment = &v.TableComment
				return
			}
		}
	}

	if !this.IsMysql() {
		tc := this.findPGTableComment()
		this.TableComment = &tc.TableComment

	}

}

//func (this *FacatroyMysql) CheckTableExist() bool {
//	if len(this.AllTables) == 0 {
//		this.AllTables = this.FindTables()
//	}
//	for _, table := range this.AllTables {
//		if table.TableName == this.Table {
//			return true
//		}
//	}
//	return false
//}

const postgres_findTable = `SELECT table_name as table_name,
						table_catalog  as table_schema
						FROM INFORMATION_SCHEMA.tables 
						WHERE table_schema='public' AND table_type IN ('BASE TABLE','VIEW')
`

func (this *FactroyPostgres) FindTables() []ichubmetadata.MetadataTable {
	var tables []ichubmetadata.MetadataTable
	if this.IsMysql() {
		sql := fmt.Sprintf(mysql_findTable, this.DbClientDto.Dbname)
		err := this.funcGetDb().Raw(sql).Find(&tables).Error
		if err != nil {
			panic(err.Error() + " :" + sql)
			return []ichubmetadata.MetadataTable{}
		}
	} else {
		sql := postgres_findTable
		err := this.funcGetDb().Raw(sql).Find(&tables).Error
		if err != nil {
			panic(err.Error() + " :" + sql)

		}
	}
	return tables
}

func (this *FactroyPostgres) FindGoType(fieldType string) (goType string) {

	goType = metadatacontext.MetadataContextInst.FindGoType(fieldType)
	return
}

func (this *FactroyPostgres) FindProtoType(fieldType string) (pbType string) {

	pbType = metadatacontext.MetadataContextInst.FindProtoType(fieldType)
	return
}
